﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

using Subgurim.Controles;

namespace CorreoSocialWeb.Models
{
    public class Ruta : IOrigenDestinoString, IOrigenDestinoLatLong
    {
        [Key]
        [ScaffoldColumn(false)]
        public int Id { get; set; }
        
        [ScaffoldColumn(false)]
        public int UsuarioId { get; set; }	

        [Required]
        public string Origen { get; set; }

        [Required]
        public string Destino { get; set; }

        [ScaffoldColumn(false)]
        [Required]
        [System.ComponentModel.DataAnnotations.Schema.ForeignKey("TamanoDePaquete")]
        public int TamanoDePaqueteId { get; set; }

        [ScaffoldColumn(false)]
        [Required]
        public int DiasDisponibles { get; set; }

        public string Notas { get; set; }

        public bool AdmiteDia(DateTime date)
        {
            if (this.DiasDisponiblesEnu == DisponibilidadDias.TodaLaSemana)
                return true;

            switch (date.DayOfWeek)
            { 
                case DayOfWeek.Monday:
                    return this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Lunes);
                
                case DayOfWeek.Tuesday:
                    return this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Martes);
                
                case DayOfWeek.Wednesday:
                    return this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Miércoles);

                case DayOfWeek.Thursday:
                    return this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Jueves);

                case DayOfWeek.Friday:
                    return this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Viernes);

                case DayOfWeek.Saturday:
                    return this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Sábado);

                case DayOfWeek.Sunday:
                    return this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Domingo);
            }

            return false;
        }

        [NotMapped]
        public double? DistanciaADestinoDesdePosicionActual
        {
            get
            {
                if (this.Usuario == null)
                    return null;

                if (!this.Usuario.TienePosicion)
                    return null;

                return DistanciaLatLong.Get(
                            this.DestinoLatitud, this.DestinoLongitud
                        , this.Usuario.PosicionActualLatitud.Value, this.Usuario.PosicionActualLongitud.Value);

            }
        }

        public static Ruta GetParaEditar(CorreoSocialDbContext db, int usuarioId, int rutaId)
        {
            return db.Rutas.FirstOrDefault(x => !x.Borrada && x.Id.Equals(rutaId) && x.UsuarioId.Equals(usuarioId));
        }

        public static Ruta GetParaEditar(int usuarioId, int rutaId)
        {
            CorreoSocialDbContext db = new CorreoSocialDbContext();

            return GetParaEditar(db, usuarioId, rutaId);
        }

        [NotMapped]
        public bool EsNueva
        {
            get
            {
                return this.Id < 1;
            }
        }

        [ScaffoldColumn(false)]
        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public DisponibilidadDias DiasDisponiblesEnu
        {
            get
            {
                return (DisponibilidadDias)this.DiasDisponibles;
            }
            set
            {
                this.DiasDisponibles = (int)value;
            }
        }

        public static List<Ruta> FiltrarParaEnvio(Envio envio, List<Ruta> rutas)
        {
            return rutas;
        }

        public string OrigenDestinoString
        {
            get
            {
                return new OrigenDestinoStringHelper().Get(this);
            }
        }

        public bool AptaEnvio(Envio envio)
        {
            return true;
        }

        [ScaffoldColumn(false)]
        [Required]
        public double OrigenLatitud { get; set; }

        [ScaffoldColumn(false)]
        [Required]
        public double OrigenLongitud { get; set; }

        [ScaffoldColumn(false)]
        [Required]
        public double DestinoLatitud { get; set; }

        [ScaffoldColumn(false)]
        [Required]
        public double DestinoLongitud { get; set; }

        public double DistanciaOrigenA(GLatLng posicion)
        {
            return new System.Device.Location.GeoCoordinate((double)this.OrigenLatitud
            , (double)this.OrigenLongitud).GetDistanceTo(new System.Device.Location.GeoCoordinate((double)posicion.lat
                ,(double) posicion.lng));
        }

        public double DistanciaDestinoA(GLatLng posicion)
        {
            return new System.Device.Location.GeoCoordinate((double)this.DestinoLatitud
            , (double)this.DestinoLongitud).GetDistanceTo(new System.Device.Location.GeoCoordinate((double)posicion.lat
                , (double)posicion.lng));
        }

        public double SumaDistanciasOrigenDestino(GLatLng origen, GLatLng destino)
        {
            return DistanciaOrigenA(origen) + DistanciaDestinoA(destino);
        }

        public bool aceptaTamaño(TamanoDePaquete tamano)
        {
            return this.TamanoDePaquete.NúmeroDeTamaño >= tamano.NúmeroDeTamaño;
        }

        public bool aceptaDias(DisponibilidadDias requerida)
        {
            return (this.DiasDisponiblesEnu & requerida) != 0;
        }

        public bool aceptaHoraSalida(int hora)
        {
            return false;
        }

        public bool aceptaHoraLlegada(int hora)
        {
            return false;
        }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public GLatLng CoordenadasOrigen
        {
            get
            {
                return new GLatLng((double)this.OrigenLatitud, (double)this.OrigenLongitud);
            }
            set
            {
                this.OrigenLatitud = value.lat;
                this.OrigenLongitud = value.lng;
            }
        }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public GLatLng CoordenadasDestino
        {
            get
            {
                return new GLatLng((double)this.DestinoLatitud, (double)this.DestinoLongitud);
            }
            set
            {
                this.DestinoLatitud = value.lat;
                this.DestinoLongitud = value.lng;
            }
        }


        //[ScaffoldColumn(false)]
        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public bool Lunes
        {
            get
            {
                return this.DiasDisponiblesEnu.HasFlag(Models.DisponibilidadDias.Lunes);
            }
            set
            {
                if (value)
                    this.DiasDisponiblesEnu |= Models.DisponibilidadDias.Lunes;
                else
                    this.DiasDisponiblesEnu &= ~DisponibilidadDias.Lunes;
            }
        }

        //[ScaffoldColumn(false)]
        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public bool Martes
        {
            get
            {
                return this.DiasDisponiblesEnu.HasFlag(Models.DisponibilidadDias.Martes);
            }
            set
            {
                if (value)
                    this.DiasDisponiblesEnu |= Models.DisponibilidadDias.Martes;
                else
                    this.DiasDisponiblesEnu &= ~DisponibilidadDias.Martes;
            }
        }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public bool Miércoles
        {
            get
            {
                return this.DiasDisponiblesEnu.HasFlag(Models.DisponibilidadDias.Miércoles);
            }
            set
            {
                if (value)
                    this.DiasDisponiblesEnu |= Models.DisponibilidadDias.Miércoles;
                else
                    this.DiasDisponiblesEnu &= ~DisponibilidadDias.Miércoles;
            }
        }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public bool Jueves
        {
            get
            {
                return this.DiasDisponiblesEnu.HasFlag(Models.DisponibilidadDias.Jueves);
            }
            set
            {
                if (value)
                    this.DiasDisponiblesEnu |= Models.DisponibilidadDias.Jueves;
                else
                    this.DiasDisponiblesEnu &= ~DisponibilidadDias.Jueves;
            }
        }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public bool Viernes
        {
            get
            {
                return this.DiasDisponiblesEnu.HasFlag(Models.DisponibilidadDias.Viernes);
            }
            set
            {
                if (value)
                    this.DiasDisponiblesEnu |= Models.DisponibilidadDias.Viernes;
                else
                    this.DiasDisponiblesEnu &= ~DisponibilidadDias.Viernes;
            }
        }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public bool Sábado
        {
            get
            {
                return this.DiasDisponiblesEnu.HasFlag(Models.DisponibilidadDias.Sábado);
            }
            set
            {
                if (value)
                    this.DiasDisponiblesEnu |= Models.DisponibilidadDias.Sábado;
                else
                    this.DiasDisponiblesEnu &= ~DisponibilidadDias.Sábado;
            }
        }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public bool Domingo
        {
            get
            {
                return this.DiasDisponiblesEnu.HasFlag(Models.DisponibilidadDias.Domingo);
            }
            set
            {
                if (value)
                    this.DiasDisponiblesEnu |= Models.DisponibilidadDias.Domingo;
                else
                    this.DiasDisponiblesEnu &= ~DisponibilidadDias.Domingo;
            }
        }

        //public decimal LatitudOrigen { get; set; }
        //public decimal LongitudOrigen { get; set; }
        //public decimal LatitudDestino { get; set; }
        //public decimal LongitudDestino { get; set; }
        //public int HoraOrigen { get; set; }
        //public int ToleranciaHoraOrigen	{ get; set; }	

        [Required]
        [Range(0, 2359)]
        public int HorarioDeSalida { get; set; }
        
        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public string HorarioDeSalidaString
        {
            get
            {
                return string.Format("{0:d}:{1:d2}", this.HoraDeSalida, this.MinutosDeSalida);
            }
        }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public string HorarioDeLlegadaString
        {
            get
            {
                return string.Format("{0:d0}:{1:d2}", this.HoraDeLlegada, this.MinutosDeLlegada);
            }
        }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        [Range(0, 23)]
        [DisplayFormat(DataFormatString = "{0:d2}")]
        public int HoraDeSalida
        {
            get
            {
                return (this.HorarioDeSalida / 100);
            }
            set
            {
                this.HorarioDeSalida = value * 100 + this.MinutosDeSalida;
            }
        }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        [Range(0, 59)]
        [DisplayFormat(DataFormatString = "{0:d2}")]
        public int MinutosDeSalida
        {
            get
            {
                return this.HorarioDeSalida % 100;
            }
            set
            {
                this.HorarioDeSalida = this.HoraDeSalida * 100 + value;
            }
        }
        
        [Required]
        [Range(0, 2359)]
        public int HorarioDeLlegada { get; set; }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        [Range(0, 23)]
        [DisplayFormat(DataFormatString="{0:d2}")]
        public int HoraDeLlegada
        {
            get
            {
                return (this.HorarioDeLlegada / 100);
            }
            set
            {
                this.HorarioDeLlegada = value * 100 + this.MinutosDeLlegada;
            }
        }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        [Range(0, 59)]
        [DisplayFormat(DataFormatString = "{0:d2}")]
        public int MinutosDeLlegada
        {
            get
            {
                return this.HorarioDeLlegada % 100;
            }
            set
            {
                this.HorarioDeLlegada = this.HoraDeLlegada * 100 + value;
            }
        }
        
        [Required]
        public bool Activa { get; set; }

        [ScaffoldColumn(false)]
        [Required]
        public bool Borrada{ get; set; }

        [Association("Usuario","UsuarioId","Id")]
        public Usuario Usuario { get; set; }

        //[Required(ErrorMessage="Valor requerido.")]
        [Association("pepe", "TamañoDePaqueteId", "Id")]
        public TamanoDePaquete TamanoDePaquete { get; set; }

        [ScaffoldColumn(false)]
        public string TamañoDePaqueteNombre
        {
            get
            {
                return TamanoDePaquete != null ? TamanoDePaquete.Nombre : "(sin tamaño)";
            }
        }

        [System.ComponentModel.DataAnnotations.Schema.NotMapped]
        public string DiasDeSemanaString
        {
            get
            {
                if (this.DiasDisponiblesEnu == DisponibilidadDias.LunesAViernes)
                    return "Lunes a Viernes";

                if (this.DiasDisponiblesEnu == DisponibilidadDias.TodaLaSemana)
                    return "Toda la Semana";

                if (this.DiasDisponiblesEnu == DisponibilidadDias.FinDeSemana)
                    return "Fines de Semana";

                if (this.DiasDisponiblesEnu == DisponibilidadDias.Ninguno)
                    return "Ninguno";

                var str = string.Empty;

                if (this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Lunes))
                    str += " Lu";

                if (this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Martes))
                    str += " Ma";

                if (this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Miércoles))
                    str += " Mi";

                if (this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Jueves))
                    str += " Ju";

                if (this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Viernes))
                    str += " Vi";

                if (this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Sábado))
                    str += " Sa";

                if (this.DiasDisponiblesEnu.HasFlag(DisponibilidadDias.Domingo))
                    str += " Do";

                return str.Trim();
            }
        }

        public double? GetDistanciaPosicionUsuarioADestino()
        {
            if (this.Usuario == null)
                return null;

            if (!this.Usuario.TienePosicion)
                return null;

            return DistanciaLatLong.Get(
                    this.Usuario.PosicionActualLatitud.Value, this.Usuario.PosicionActualLongitud.Value
                    , this.DestinoLatitud, this.DestinoLongitud);
        }

        [NotMapped]
        public bool FueraDeRuta
        {
            get
            {
                if (this.Usuario == null)
                    return true;

                if (!this.Usuario.TienePosicion)
                    return true;

                return this.GetDistanciaPosicionUsuarioADestino() > this.GetDistanciaOrigenDestino();
            }
        }
       
        public double GetDistanciaOrigenDestino()
        {
            return DistanciaLatLong.Get(
                    this.OrigenLatitud, this.OrigenLongitud
                    , this.DestinoLatitud, this.DestinoLongitud);
        }

        public string GetResumenEnvios()
        {
            var sol = this.Envios.Count(x => x.EstadoEnu == EstadoDeRutaDeEnvio.Solicitada);
            var rech = this.Envios.Count(x => x.EstadoEnu == EstadoDeRutaDeEnvio.Rechazada);
            var despPen = this.Envios.Count(x => x.EstadoEnu == EstadoDeRutaDeEnvio.Aceptada && x.Envio.EstadoEnu == EstadoDeEnvioEnu.DespachoPendiente);
            var enTran = this.Envios.Count(x => x.EstadoEnu == EstadoDeRutaDeEnvio.Aceptada && (x.Envio.EstadoEnu == EstadoDeEnvioEnu.EnTransito || x.Envio.EstadoEnu == EstadoDeEnvioEnu.Cancelado));
            var fin = this.Envios.Count(x => x.EstadoEnu == EstadoDeRutaDeEnvio.Finalizada);
            var can = this.Envios.Count(x => x.EstadoEnu == EstadoDeRutaDeEnvio.Cancelada);

            return string.Format("{0}{1}{2}{3}{4}"
                , sol > 0 ? string.Format("{0} Solic.", sol) : string.Empty
                , rech > 0 ? string.Format("{0} Rech.", rech) : string.Empty
                , despPen > 0 ? string.Format("{0} Despacho Pend.",despPen) : string.Empty
                , enTran > 0 ? string.Format("{0} En Trans.",enTran) : string.Empty
                , fin > 0 ? string.Format("{0} Final.",fin) : string.Empty
                , can > 0 ? string.Format("{0} Cancel.",can) : string.Empty
                );
        }

        public bool TieneEnvios
        {
            get
            {
                return this.Envios.Any();
            }
        }

        [Association("Envios", "Id", "RutaId")]
        public List<RutaDeEnvio> Envios { get; set; }

        public static List<Ruta> GetLista(int usuarioId, bool activas, bool inactivas, OpcionesDePaginacion opcionesDePaginacion)
        {
            var resultados = new CorreoSocialDbContext().Rutas
                .Include(x => x.TamanoDePaquete)
                .Include(x => x.Envios.Select(y => y.Envio))
                .Where(x => !x.Borrada && x.UsuarioId.Equals(usuarioId))
                .Where(x => x.Activa && activas || !x.Activa && inactivas)
                .OrderByDescending(x => x.Id)
                .ToList();

            opcionesDePaginacion.SetTotalCount(resultados.Count());

            return resultados
                .Skip(opcionesDePaginacion.ItemsASaltar)
                .Take(opcionesDePaginacion.ItemsATomar).ToList();
        }

        public static Ruta GetParaDetalle(int rutaId)
        {
            return new CorreoSocialDbContext().Rutas
                .Include(x=>x.Usuario)
                .Include(x=>x.TamanoDePaquete)
                .Include(x => x.Envios.Select(y=>y.Envio))
                .Include(x => x.Envios.Select(y => y.Ruta.Usuario))
                .Include(x => x.Envios.Select(y => y.Envio.Usuario))
                .Include(x => x.Envios.Select(y => y.Envio.TamanoDePaquete))
                .FirstOrDefault(x => !x.Borrada && x.Id.Equals(rutaId));
        }

        [NotMapped]
        public string HoraDeSalidaString
        {
            get
            {
                return string.Format("{0:d2}", this.HoraDeSalida);
            }
        }

        [NotMapped]
        public string HoraDeLlegadaString
        {
            get
            {
                return string.Format("{0:d2}", this.HoraDeLlegada);
            }
        }

        [NotMapped]
        public string MinutosDeSalidaString
        {
            get
            {
                return string.Format("{0:d2}", this.MinutosDeSalida);
            }
        }

        [NotMapped]
        public string MinutosDeLlegadaString
        {
            get
            {
                return string.Format("{0:d2}", this.MinutosDeLlegada);
            }
        }

        [NotMapped]
        public string NombreConMensajero
        {
            get
            {
                return string.Format("\"{0}\" {1}"
                    , this.OrigenDestinoString
                    , this.Usuario.NombreReputacionString
                    );
            }
        }

        public static void Borrar(int usuarioId, int rutaId)
        {
            var db = new CorreoSocialDbContext();

            var ruta = db.Rutas.FirstOrDefault(x => x.Id.Equals(rutaId) && x.UsuarioId.Equals(usuarioId) && !x.Borrada);

            ruta.Borrada = true;

            db.SaveChanges();
        }

        [NotMapped]
        public bool PuedeBorrar
        {
            get
            {
                return !this.TieneEnviosPendientes;
            }
        }

        [NotMapped]
        public bool TieneEnviosPendientes
        {
            get
            {
                return this.Envios.Any(x=>x.EstaPendiente);
            }
        }
        

        [NotMapped]
        public bool PuedeModificar
        {
            get
            {
                return true;
            }
        }
    }
}